verify-email-form.tsx 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. 'use client';
  2. import { useEffect, useState } from 'react';
  3. import { useSearchParams, useRouter } from 'next/navigation';
  4. import Link from 'next/link';
  5. import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card';
  6. import { Button } from '@/components/ui/button';
  7. import { CheckCircle, XCircle, Loader2 } from 'lucide-react';
  8. import { useTranslations } from "next-intl";
  9. interface VerifyEmailFormProps {
  10. locale: string;
  11. }
  12. export default function VerifyEmailForm({ locale }: VerifyEmailFormProps) {
  13. const [status, setStatus] = useState<'loading' | 'success' | 'error'>('loading');
  14. const [message, setMessage] = useState('');
  15. const searchParams = useSearchParams();
  16. const router = useRouter();
  17. const t = useTranslations("auth.verifyEmail");
  18. const tErrors = useTranslations("auth.errors");
  19. useEffect(() => {
  20. const token = searchParams.get('token');
  21. if (!token) {
  22. setStatus('error');
  23. setMessage(t('invalidToken'));
  24. return;
  25. }
  26. // 验证邮箱
  27. const verifyEmail = async () => {
  28. try {
  29. const response = await fetch(`/api/auth/verify-email?token=${token}`);
  30. const data = await response.json();
  31. if (response.ok) {
  32. setStatus('success');
  33. setMessage(t('success'));
  34. } else {
  35. setStatus('error');
  36. setMessage(data.error || tErrors('verificationFailed'));
  37. }
  38. } catch (error) {
  39. setStatus('error');
  40. setMessage(tErrors('networkError'));
  41. }
  42. };
  43. verifyEmail();
  44. }, [searchParams, t, tErrors]);
  45. const handleGoToLogin = () => {
  46. router.push(`/${locale}/auth/login`);
  47. };
  48. return (
  49. <div className="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
  50. <Card className="w-full max-w-md">
  51. <CardHeader className="text-center space-y-4">
  52. <div className="flex justify-center">
  53. {status === 'loading' && (
  54. <div className="rounded-full bg-blue-100 p-4">
  55. <Loader2 className="h-8 w-8 text-blue-600 animate-spin" />
  56. </div>
  57. )}
  58. {status === 'success' && (
  59. <div className="rounded-full bg-green-100 p-4">
  60. <CheckCircle className="h-8 w-8 text-green-600" />
  61. </div>
  62. )}
  63. {status === 'error' && (
  64. <div className="rounded-full bg-red-100 p-4">
  65. <XCircle className="h-8 w-8 text-red-600" />
  66. </div>
  67. )}
  68. </div>
  69. <CardTitle className="text-2xl font-bold">
  70. {status === 'loading' && t('verifying')}
  71. {status === 'success' && t('success')}
  72. {status === 'error' && t('error')}
  73. </CardTitle>
  74. <CardDescription>
  75. {message}
  76. </CardDescription>
  77. </CardHeader>
  78. {status !== 'loading' && (
  79. <CardContent className="space-y-4">
  80. {status === 'success' && (
  81. <div className="bg-green-50 p-4 rounded-lg">
  82. <div className="text-sm text-green-800">
  83. <p className="font-medium">{t('successTitle')}</p>
  84. <ul className="mt-2 space-y-1">
  85. <li>• {t('canLogin')}</li>
  86. <li>• {t('accessFeatures')}</li>
  87. <li>• {t('enjoyService')}</li>
  88. </ul>
  89. </div>
  90. </div>
  91. )}
  92. {status === 'error' && (
  93. <div className="bg-red-50 p-4 rounded-lg">
  94. <div className="text-sm text-red-800">
  95. <p className="font-medium">{t('possibleReasons')}</p>
  96. <ul className="mt-2 space-y-1">
  97. <li>• {t('linkExpired')}</li>
  98. <li>• {t('linkInvalid')}</li>
  99. <li>• {t('alreadyVerified')}</li>
  100. </ul>
  101. </div>
  102. </div>
  103. )}
  104. </CardContent>
  105. )}
  106. {status !== 'loading' && (
  107. <CardFooter className="flex flex-col space-y-3">
  108. {status === 'success' && (
  109. <Button onClick={handleGoToLogin} className="w-full">
  110. {t('backToLogin')}
  111. </Button>
  112. )}
  113. {status === 'error' && (
  114. <>
  115. <Button asChild className="w-full">
  116. <Link href={`/${locale}/auth/register`}>
  117. {t('registerAgain')}
  118. </Link>
  119. </Button>
  120. <Link href={`/${locale}/auth/login`} className="text-center text-sm text-blue-600 hover:underline">
  121. {t('backToLogin')}
  122. </Link>
  123. </>
  124. )}
  125. </CardFooter>
  126. )}
  127. </Card>
  128. </div>
  129. );
  130. }